/*******************************************************************************
 * Copyright (c) 2005 Koji Hisano <hisano@users.sourceforge.net>
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Common Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/cpl-v10.html
 *
 * Contributors:
 *     Koji Hisano - initial API and implementation
 *******************************************************************************/
package jp.sf.mapswidgets;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;

import org.eclipse.swt.graphics.RGB;

/**
 * Instances of this class represent a vector polyline overlay on the map.
 * <p>
 * A polyline is drawn with the vector drawing facilities of the browser if they are available or an image overlay from Google servers otherwise.
 * </p>
 * <p>
 * See Google Maps API documentation [<a href="http://www.google.com/apis/maps/documentation/#GPolyline_code_">Class Reference &gt; GPolyline</a>].
 * </p>
 *
 * @see Marker
 */
public final class Polyline extends Overlay {
	private List<Point> points = new PointsList();
	private RGB color;
	private int lineWidth;
	private double opacity;

	/**
	 * Construct a new instance of this class given the points.
	 *
	 * @param points the points
	 */
	public Polyline(Point points[]) {
		this(points, new RGB(0, 0, 0xFF));
	}
	
	/**
	 * Construct a new instance of this class given the points and the color.
	 *
	 * @param points the points
	 * @param color the color
	 */
	public Polyline(Point points[], RGB color) {
		this(points, color, 5);
	}
	
	/**
	 * Construct a new instance of this class given the points, the color and the width of the line.
	 *
	 * @param points the points
	 * @param color the color
	 * @param lineWidth the width of the line in pixels
	 */
	public Polyline(Point points[], RGB color, int lineWidth) {
		this(points, color, lineWidth, 0.45f);
	}
	
	/**
	 * Construct a new instance of this class given the points, the color , the width of the line and the opacity.
	 *
	 * @param points the points
	 * @param color the color
	 * @param lineWidth the width of the line in pixels
	 * @param opacity the opacity
	 */
	public Polyline(Point points[], RGB color, int lineWidth, double opacity) {
		setPointsFieldOnly(points);
		setColorFieldOnly(color);
		this.lineWidth = lineWidth;
		this.opacity = opacity;
	}

	/**
	 * Get the points.
	 * @return the points
	 */
	public Point[] getPoints() {
		return points.toArray(new Point[0]);
	}
	
	/**
	 * Get the inner points list.
	 * <p>
	 * Changes to this list are reflected immediately.
	 * </p>
	 * @return the points
	 */
	public List<Point> getPointsList() {
		return points;
	}
	
	/**
	 * Set the points
	 * @param points the points
	 */
	public void setPoints(Point[] points) {
		setPointsFieldOnly(points);
		reset();
	}

	private void setPointsFieldOnly(Point[] points) {
		CheckUtils.isNotNull("points", points);
		this.points.clear();
		this.points.addAll(Arrays.asList(points));
	}
	
	/**
	 * Get the color.
	 * @return the color
	 */
	public RGB getColor() {
		return color;
	}
	
	/**
	 * Set the color
	 * @param color the color
	 */
	public void setColor(RGB color) {
		setColorFieldOnly(color);
		reset();
	}

	private void setColorFieldOnly(RGB color) {
		CheckUtils.isNotNull("color", color);
		this.color = color;
	}
	
	/**
	 * Get the width of the line.
	 * @return the width of the line
	 */
	public int getLineWidth() {
		return lineWidth;
	}
	
	/**
	 * Set the width of the line
	 * @param lineWidth the width of the line
	 */
	public void setLineWidth(int lineWidth) {
		this.lineWidth = lineWidth;
		reset();
	}
	
	/**
	 * Get the opacity.
	 * @return the opacity
	 */
	public double getOpacity() {
		return opacity;
	}
	
	/**
	 * Set the opacity.
	 * @param opacity the opacity
	 */
	public void setOpacity(double opacity) {
		this.opacity = opacity;
		reset();
	}

	private void reset() {
		if (isAdded()) {
			GoogleMaps widget = getWidget();
			widget.removeOverlay(this);
			widget.addOverlay(this);
		}
	}

	/**
	 * Removes this polyline form the map.
	 */
	public void dispose() {
		if (isAdded()) {
			getWidget().removeOverlay(this);
		}
	}

	String getId() {
		return "Polyline" + super.getId();
	}

	String getExpressionToCreate() {
		return "new GPolyline(" + getPointsExpressoin() + ", " + getColorExpression() + ", " + getLineWidth() + ", " + getOpacity() + ")";
	}
	
	private String getPointsExpressoin() {
		Point[] points = getPoints();
		if (points.length == 0) {
            return "[]";
		}
 
        StringBuilder expression = new StringBuilder();
        for (int i = 0; i < points.length; i++) {
            if (i == 0) {
                expression.append('[');
            } else {
                expression.append(", ");
            }
            expression.append(points[i].getExpression());
        }
        expression.append("]");
        return expression.toString();
	}

	private String getColorExpression() {
		RGB rgb = getColor();
		return String.format("'#%02x%02x%02x'", rgb.red, rgb.green, rgb.blue);
	}
	
	private class PointsList extends ArrayList<Point> {
		public void add(int index, Point element) {
			super.add(index, element);
			reset();
		}

		public boolean add(Point o) {
			boolean result = super.add(o);
			reset();
			return result;
		}

		public boolean addAll(Collection<? extends Point> c) {
			boolean result = super.addAll(c);
			reset();
			return result;
		}

		public boolean addAll(int index, Collection<? extends Point> c) {
			boolean result = super.addAll(index, c);
			reset();
			return result;
		}

		public void clear() {
			super.clear();
			reset();
		}

		public Point remove(int index) {
			Point result = super.remove(index);
			reset();
			return result;
		}

		public boolean remove(Object o) {
			boolean result = super.remove(o);
			reset();
			return result;
		}

		public Point set(int index, Point element) {
			Point result = super.set(index, element);
			reset();
			return result;
		}
		
	}
}
